package com.scu.phototab.controller;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.base.Strings;
import com.scu.phototab.bean.Token;
import com.scu.phototab.bean.User;
import com.scu.phototab.enums.ResponseEnum;
import com.scu.phototab.exception.ServerExpection;
import com.scu.phototab.repository.UserRepository;
import com.scu.phototab.service.MailServiceImpl;
import com.scu.phototab.util.MD5;
import com.scu.phototab.util.Predictions;
import com.scu.phototab.util.ResponseUtil;
import com.scu.phototab.util.UserUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.PostConstruct;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashSet;

import static com.scu.phototab.util.Predictions.checkNotNull;

/**
 * Created by tianfei on 2017/4/6.
 */
@RestController
@RequestMapping(value = "/session")
public class UserController {

    private static Logger logger= LoggerFactory.getLogger(UserController.class);

    @Autowired
    private UserRepository userRepository;
    private static String SERVER_SECRET;


    @Value("${serverSecret}")
    public void setServerSecret(String secret){
        UserController.SERVER_SECRET = secret;
    }

    //Algorithm参数可重用，设置为静态可以避免每一次用户请求都初始化一个Algorithm，减少响应时间
    private static Algorithm algorithm = null;
    private static JWTVerifier verifier = null;


    @PostConstruct
    public void initAlgorithm(){
        try {
            algorithm = Algorithm.HMAC256(SERVER_SECRET);

            verifier = JWT.require(algorithm)
                    .withIssuer("tianff.net")
                    .build(); //Reusable verifier instance
        } catch (UnsupportedEncodingException e) {
            throw new ServerExpection(ResponseEnum.UNKONW_ERROR);
        }

    }

    @PostMapping(value = "/user/login")
    public Object userLoginWithPassword (@RequestParam String email,
                                         @RequestParam String MD5pass){

        String _email=checkNotNull(email);
        String _password=checkNotNull(MD5pass);

        User user=userRepository.findByEmail(_email);

        if (user==null){
            throw new ServerExpection(ResponseEnum.USER_NOT_FOUND);
        }

        if (!user.getPasswordMD5().equals(_password)){

            //接受大黄的建议，全部抛出异常
            //return ResponseUtil.error(ResponseEnum.PASSWORD_ERROR);
            throw new ServerExpection(ResponseEnum.PASSWORD_ERROR);

        }else {
            //成功后返回用户一个token（一组token）

            // Signature使用HS256

            /******************客户端必须在header的authorization字段中添加token**********************
             ******************authorization格式为： Authorization: Bearer 'token'*****************
             ******************          Bearer必须存在    修改- 不添加Bearer      *****************
             ******************     RFC 6750   OAuth 2.0 Bearer Token Usage      *****************
             */

            String token= createToken7day(user.getId());

            //设置User的tokenValid为TRUE
            user.setTokenValid(true);
            if (userRepository.save(user) == null){
                throw  new ServerExpection(ResponseEnum.UNKONW_ERROR);
            }

            return ResponseUtil.success(new Token(token));
        }
    }

    @PostMapping(value = "/user/create")
    public Object userRegister(@RequestParam(value = "email") String email,
                           @RequestParam(value = "MD5pass") String MD5pass){

        String _email=checkNotNull(email, ResponseEnum.REGISTER_ERR);
        String _MD5pass=checkNotNull(MD5pass, ResponseEnum.REGISTER_ERR);
        //String _name=checkNotNull(name, ResponseEnum.REGISTER_ERR);

        if (!Predictions.isEmailAddress(_email)){
            throw new ServerExpection(ResponseEnum.WRONG_EMAIL_ADD);
        }

        User user=new User();
        user.setEmail(_email);
        user.setName(_email);
        user.setTokenValid(false);
        user.setPasswordMD5(_MD5pass);

        User select=userRepository.findByEmail(_email);
        if (select!=null){
            throw new ServerExpection(ResponseEnum.USER_EXISTED);
        }

        User save=userRepository.save(user);
        if (save==null){
            throw new ServerExpection(ResponseEnum.UNKONW_ERROR);
        }

        //返回的数据data字段为空
        return ResponseUtil.success();
    }


    //////////////////////////////////////////////////////////////////////////
    //////////为确保一致性，所有的token相关的操作的函数名一律为tokenXXX////////////
    //////////为确保一致性，所有的token相关的操作的映射路径一律为/token/XXX////////
    /////////////////////////////////////////////////////////////////////////

    /*
     客户端使用用户名跟密码请求登录
     服务端收到请求，去验证用户名与密码
     验证成功后，服务端会签发一个 Token，再把这个 Token 发送给客户端 （注意服务端并不保存这个token）
     客户端收到 Token 以后把它存储起来
     客户端每次向服务端请求资源的时候需要带着服务端签发的 Token

     ******************客户端必须在header的authorization字段中添加token**********************
     ******************authorization格式为： Authorization: Bearer 'token'*****************
     ******************                 Bearer必须存在                    *****************
     ******************     RFC 6750   OAuth 2.0 Bearer Token Usage      *****************

     服务端收到请求，然后去验证客户端请求里面带着的 Token，如果验证成功，就向客户端返回请求的数据
     */

    /*
    JWT token格式：

    header.payload.signature   中间用点分隔开，并且都使用 Base64 编码

    header 部分主要是两部分内容，一个是 Token 的类型，另一个是使用的算法，比如下面类型就是 JWT，使用的算法是 HS256。
    {
      "typ": "JWT",
      "alg": "HS256"
    }

    本服务器采用的payload格式为
    {
     "iss": "tianff.net",
     "exp": "1438955445",
     "uid": "123",
    }

    Signature
    先用 Base64 编码 header.payload ，再用加密算法加密一下，
    加密的时候要放进去一个 Secret ，这个密码秘密地存储在服务端。
     */


    //更新用户信息
    @PostMapping(value = "/token/update")
    public Object userUpdate(@RequestParam(value = "id") Integer uid,
                             @RequestParam(value = "name",required = false,defaultValue = "") String name,
                             @RequestParam(value = "sig",required = false,defaultValue = "")String signature){

        User user=userRepository.findOne(uid);
        if (user == null){
            throw new ServerExpection(ResponseEnum.USER_NOT_FOUND);
        }

        if (Strings.isNullOrEmpty(name)){
            user.setName(name);
        }

        if (!Strings.isNullOrEmpty(signature)){
            user.setSignature(signature);
        }

        userRepository.save(user);

        return ResponseUtil.success();
    }

    //查看用户详细信息
    @PostMapping(value = "/token/info")
    public Object userCheckInfo(HttpServletRequest request){

//        String token =request.getHeader("authorization");
//        DecodedJWT jwt = verifier.verify(token);
//
//        Integer uid=jwt.getClaim("uid").asInt();
        Integer uid=(Integer)request.getSession().getAttribute("uid");

        User user=userRepository.findOne(uid);
        if (!user.isTokenValid()){
            throw new ServerExpection(ResponseEnum.OVER_TIME_TOKEN);
        }

        return ResponseUtil.success(UserUtil.change2RetUser(user));
    }

    //登出
    @PostMapping(value = "/token/logout")
    public Object userLogout(HttpServletRequest request){

//        String token =request.getHeader("authorization");
//        DecodedJWT jwt = verifier.verify(token);
//
//        Integer uid=jwt.getClaim("uid").asInt();

        Integer uid=(Integer)request.getSession().getAttribute("uid");

        User user=userRepository.findOne(uid);
        user.setTokenValid(false);

        userRepository.save(user);
        return  ResponseUtil.success();
    }


    @PostMapping(value = "/token/login")
    public Object tokenLogin(HttpServletRequest request){

//        String token =request.getHeader("authorization");
//        DecodedJWT jwt = verifier.verify(token);

//        Integer uid=jwt.getClaim("uid").asInt();

        Integer uid=(Integer)request.getSession().getAttribute("uid");

        User user=userRepository.findOne(uid);
        if (!user.isTokenValid()){
            throw new ServerExpection(ResponseEnum.REQUEST_RE_LOGIN);
        }

        return ResponseUtil.success();
    }

    //刷新token不经过JWTFilter!!!!!
    @PostMapping(value = "/refresh/token")
    public Object tokenRefresh(HttpServletRequest request){

        String token =request.getHeader("authorization");
        DecodedJWT jwt = verifier.verify(token);

        Integer uid=jwt.getClaim("uid").asInt();

        User user=userRepository.findOne(uid);
        if (!user.isTokenValid()){
            throw new ServerExpection(ResponseEnum.REQUEST_RE_LOGIN);
        }
        String newToken= createToken7day(uid);

        return ResponseUtil.success(new Token(newToken));
    }

    ///////////////////////找回密码/////////////////////////////
    /////////////找回密码不需要验证token，故前缀为/user///////////
    @Autowired
    private MailServiceImpl mailService;

    //fixme 修改存储正在请求修改密码的用户的策略
    private static final HashSet<Integer> requestNewPasswordUserSet = new HashSet<>();

    /*
       向注册邮箱发送一封邮件
     */
    @PostMapping(value = "/user/pass/send1email")
    public Object sendFindPassEmail(@RequestParam(value = "email")String email){

        //check empty
        if (Strings.isNullOrEmpty(email)){
            throw new ServerExpection(ResponseEnum.BAD_REQUEST_PARAM);
        }

        User user=userRepository.findByEmail(email);

        if (user == null){
            throw new ServerExpection(ResponseEnum.USER_NOT_FOUND);
        }

        String token = createToken(user.getId(), 1);
        try {
            mailService.sendTokenMail(token,email);
        } catch (MessagingException e) {
            e.printStackTrace();
            logger.error(e.toString());
            return ResponseUtil.error(ResponseEnum.UNKONW_ERROR);
        }

        requestNewPasswordUserSet.add(user.getId());

        return ResponseUtil.success();
    }

    /*
      验证链接是不是由邮件跳转过来的（验证token）
     */
    @RequestMapping(value = "/user/pass/confirm",method = RequestMethod.GET)
    public ModelAndView findPwdConfirmURL(HttpServletRequest request,String t){

        DecodedJWT jwt=null;
        try {
            jwt=verifier.verify(t);
        }catch (Exception e){
            throw new ServerExpection(ResponseEnum.URL_OVER_TIME);
        }

        Integer uid=jwt.getClaim("uid").asInt();
        if (!requestNewPasswordUserSet.contains(uid)){
            throw new ServerExpection(ResponseEnum.URL_HAS_BEEN_USED);
        }

        request.getSession().setAttribute("uid",uid);
        return new ModelAndView("forward:/web/newpass");
    }

    /**
     * 根据Web页面结果设置新密码
     */
    @PostMapping(value = "/user/pass/new")
    public String findPwdsetNewPassword(HttpServletRequest request,
                                 String newpwd) {

        Integer uid=(Integer)request.getSession().getAttribute("uid");

        if (!requestNewPasswordUserSet.contains(uid)){
            throw new ServerExpection(ResponseEnum.URL_HAS_BEEN_USED);
        }

        logger.error(uid+"");

        User user = userRepository.findOne(uid);

        user.setPasswordMD5(MD5.getStringMD5(newpwd));
        userRepository.save(user);

        requestNewPasswordUserSet.remove(uid);
        return "200";
    }


    private String createToken7day(Integer uid){
        //设置7天后过期
        return createToken(uid,7*24);

    }

    private String createToken(Integer uid, int hourCount){
        Date exp=new Date(System.currentTimeMillis() + hourCount*3600l*1000l);

        return JWT.create()
                .withIssuer("tianff.net")
                .withExpiresAt(exp)
                .withClaim("uid",uid)

                .sign(algorithm);
    }
}
